import { Client } from '@larksuiteoapi/node-sdk';
import * as fs from 'fs';

interface LarkUser {
  name: string;
  en_name?: string;
  email: string;
  user_id: string;
  department_ids?: string[];
}

/**
 * LarkClient class for Lark Suite integration
 */
class LarkClient {
  private client: Client;
  // Create rate limiters for different API endpoints
  private uploadRateLimiter: any;
  private readonly API_DELAY = 1000; // 基础延迟1秒
  private readonly MAX_RETRIES = 5; // 最大重试次数

  constructor(appId: string, appSecret: string) {
    // Initialize the Lark client with app credentials
    // You can pass credentials directly or use environment variables as fallback
    this.client = new Client({
      appId: appId ,
      appSecret: appSecret,
      disableTokenCache: false,
    });
    this.uploadRateLimiter = this.createRateLimiter(4, 250); // 4 calls per second. Lark limit 5 calls per second.
  }

  /**
   * Get user information by user ID
   */
  async getUserInfo(userId: string): Promise<LarkUser | null> {
    try {
      const response = await this.client.contact.user.get({
        params: {
          user_id_type: 'open_id', // or 'union_id', 'user_id' depending on your needs
        },
        path: {
          user_id: userId // Move user_id to path property as required by the API
        }
      });

      if (response.data && response.data.user) {
        const userData = response.data.user;
        return {
          name: userData.name || '',
          en_name: userData.en_name,
          email: userData.email || '',
          user_id: userData.user_id || userData.open_id || '',
          department_ids: userData.department_ids,
        };
      }
      return null;
    } catch (error) {
      console.error('Error getting user info:', error);
      return null;
    }
  }

  /**
   * Send a message to a user or chat
   */
  async sendMessage(receiveId: string, content: string, msgType: string = 'text'): Promise<boolean> {
    try {
      let messageContent: string;

      // Format the content based on message type
      if (msgType === 'text') {
        messageContent = JSON.stringify({ text: content });
      } else if (msgType === 'post') {
        // For rich text messages (post)
        messageContent = JSON.stringify({
          zh_cn: {
            title: 'Message from Automation',
            content: [
              [
                {
                  tag: 'text',
                  text: content,
                },
              ],
            ],
          },
        });
      } else {
        throw new Error(`Unsupported message type: ${msgType}`);
      }

      const response = await this.client.im.message.create({
        params: {
          receive_id_type: 'open_id', // or 'user_id', 'union_id', 'chat_id' depending on your needs
        },
        data: {
          receive_id: receiveId,
          msg_type: msgType,
          content: messageContent,
        },
      });

      return !!response.data;
    } catch (error) {
      console.error('Error sending message:', error);
      return false;
    }
  }
  

  async safeBitableOperation(operation: Function, ...args: any[]) {
    let retry = 0;
    const maxRetries = 5; // 增加重试次数

    while (retry < maxRetries) {
      try {
        // 基础延迟
        await new Promise(resolve =>
          setTimeout(resolve, this.API_DELAY * (retry + 1)));

        const result = await operation(...args);
        return result;

      } catch (error: unknown) {
        const errorObj = error as any;

        // 检查各种错误类型
        const isLockError = errorObj.code === 1254291 ||
                           (errorObj.response?.data?.code === 1254291) ||
                           (errorObj.message && errorObj.message.includes('LockNotObtainedError'));

        const isRateLimit = errorObj.code === 99991400 ||
                           (errorObj.response?.data?.code === 99991400) ||
                           (errorObj.message && errorObj.message.includes('frequency limit'));

        const isRetryableError = errorObj.code === 1254002 || isLockError || isRateLimit;

        if (isRetryableError && retry < maxRetries - 1) {
          retry++;

          // 根据错误类型设置不同的等待时间
          let waitTime;
          if (isLockError) {
            // 锁定错误：较长等待时间 + 随机性
            waitTime = (3000 + Math.random() * 2000) * retry;
            console.log(`⚠️ 数据表锁定，等待 ${(waitTime/1000).toFixed(1)}秒 重试${retry}/${maxRetries}...`);
          } else if (isRateLimit) {
            // 频率限制：指数退避
            waitTime = Math.min(2000 * Math.pow(2, retry), 30000);
            console.log(`⚠️ API频率限制，等待 ${(waitTime/1000).toFixed(1)}秒 重试${retry}/${maxRetries}...`);
          } else {
            // 其他错误：线性增长
            waitTime = 1000 * retry;
            console.log(`⚠️ 操作失败，等待 ${(waitTime/1000).toFixed(1)}秒 重试${retry}/${maxRetries}...`);
          }

          await new Promise(resolve => setTimeout(resolve, waitTime));
          continue;
        }

        // 不可重试的错误或达到最大重试次数
        if (isLockError) {
          throw new Error(`数据表锁定错误，已重试${maxRetries}次仍无法获取锁定`);
        } else if (isRateLimit) {
          throw new Error(`API频率限制错误，已重试${maxRetries}次仍被限制`);
        } else {
          throw error;
        }
      }
    }
  }

  async appendDataToBitable (appToken: string, tableId: string, newRecords: any[], options: { ignore_consistency_check?: boolean, batchSize?: number } = {}) {
    const { ignore_consistency_check = false, batchSize = 50 } = options; // Change default to 50
    const bitableRecords: { fields: any }[] = [];
    
    for (let i = 0; i < newRecords.length; i++) {
      bitableRecords.push({
        fields: newRecords[i],
      });
  
      if (bitableRecords.length >= batchSize || i == newRecords.length - 1) {
        const result = await this.safeBitableOperation(async () => {
            return this.client.bitable.appTableRecord.batchCreate({
                path: {
                  app_token: appToken,
                  table_id: tableId,
                },
                params: {
                  ignore_consistency_check: ignore_consistency_check,
                },
                data: {
                  records: bitableRecords
                },
              }
            );
          }
        );
        if (result.code !== 0) {
          throw new Error("Append data to bitable error: " + JSON.stringify(result));
        }      
  
        bitableRecords.length = 0;
      }
    }  
  }
  
  async readBitable(appToken: string, tableId: string, filter: any = null, maxRecord: number = 0, sort: any = null) {
    return this.safeBitableOperation(async () => {
      const allRecords: Record<string, any>[] = [];
      let pageToken: string | undefined = undefined;
      let hasMore = true;

      while (hasMore) {
        const result = await this.client.bitable.appTableRecord.search({
          path: {
            app_token: appToken,
            table_id: tableId,
          },
          params: {
            page_size: 500, // 每页获取500条记录（API允许的最大值）
            ...(pageToken && { page_token: pageToken }),
          },
          data: {
            ...(filter && { filter: filter }),
            ...(sort && { sort: sort })
          },
        });

        if (result.code !== 0) {
          console.error(`Read bitable error for appToken: ${appToken}, tableId: ${tableId}, error: ${JSON.stringify(result)}`);
          throw new Error("Read bitable error: " + JSON.stringify(result));
        }

        if (result.data?.items) {
          for (const item of result.data.items) {
            const record: Record<string, any> = {};
            for (const field in item.fields) {
              record[field] = item.fields[field];
            }
            record['record_id'] = item.record_id;
            allRecords.push(record);
            
            if (maxRecord > 0 && allRecords.length >= maxRecord) {
              hasMore = false;
              break;
            }
          }
        }

        hasMore = hasMore && (result.data?.has_more || false);
        pageToken = result.data?.page_token;

        if (!pageToken) {
            hasMore = false;
        }
      }
      
      if (maxRecord > 0) {
          return allRecords.slice(0, maxRecord);
      }

      return allRecords;
    });
  }

  async updateBitable(appToken: string, tableId: string, recordId: string, newFields: any) {
    const result = await this.client.bitable.appTableRecord.update({
      path: {
        app_token: appToken,
        table_id: tableId,
        record_id: recordId,
      },
      data: {
        fields: newFields,
      }
    });

    if (result.code !== 0) {
      throw new Error("Update bitable error: " + JSON.stringify(result));
    }
  }

  async updateBitableBatch(appToken: string, tableId: string, records: any[], batchSize = 100) {
    const result = await this.client.bitable.appTableRecord.batchUpdate({
      path: {
        app_token: appToken,
        table_id: tableId,
      },
      data: {
        records: records.map(record => ({
          record_id: record.record_id,
          fields: record.fields,
        })),
      },
    });
    if (result.code !== 0) {
      throw new Error("Update bitable batch error: " + JSON.stringify(result));
    }
  }
  
  // Rate limiter for uploadFileToBitable - 5 calls per second
  private createRateLimiter(maxConcurrent: number = 5, minInterval: number = 200) {
    return {
      queue: [] as (() => void)[],
      inProgress: 0,
      maxConcurrent,
      lastCallTime: 0,
      minInterval, // Default: 200ms between calls = 5 calls per second
      
      async execute<T>(fn: () => Promise<T>): Promise<T> {
        return new Promise((resolve, reject) => {
          const task = async () => {
            this.inProgress++;
            try {
              // Ensure minimum time between calls
              const now = Date.now();
              const elapsed = now - this.lastCallTime;
              if (elapsed < this.minInterval) {
                await new Promise(r => setTimeout(r, this.minInterval - elapsed));
              }
              
              this.lastCallTime = Date.now();
              const result = await fn();
              resolve(result);
            } catch (error) {
              reject(error);
            } finally {
              this.inProgress--;
              this.processQueue();
            }
          };
          
          if (this.inProgress < this.maxConcurrent) {
            task();
          } else {
            console.log('Rate limit exceeded, adding task to queue');
            this.queue.push(task);
          }
        });
      },
      
      processQueue() {
        if (this.queue.length > 0 && this.inProgress < this.maxConcurrent) {
          const next = this.queue.shift();
          if (next) next();
        }
      }
    };
  }
  

  async uploadFileToBitable(filePath: string, appToken: string): Promise<string> {
    return this.uploadRateLimiter.execute(async () => {
      const fileName = filePath.split('/').pop() || 'none_name';
      const maxRetries = 3;
      let retryCount = 0;

      while (retryCount < maxRetries) {
        try {
          const fileSize = fs.statSync(filePath).size;
          const file = fs.createReadStream(filePath);
          const resp = await this.client.drive.v1.media.uploadAll({
            data: {
              file_name: fileName,
              parent_type: 'bitable_image',
              parent_node: appToken,
              size: fileSize,
              file: file,
            },
          });

          if (resp?.file_token) {
            return resp.file_token;
          } else {
            throw new Error('未获取到文件token');
          }

        } catch (error: any) {
          retryCount++;

          // 检查是否是频率限制错误
          if (error.response?.data?.code === 99991400 ||
              (error.message && error.message.includes('frequency limit'))) {
            if (retryCount < maxRetries) {
              const waitTime = Math.min(3000 * Math.pow(2, retryCount), 15000);
              console.log(`⚠️ 上传API限制，等待${waitTime/1000}秒重试...`);
              await new Promise(resolve => setTimeout(resolve, waitTime));
              continue;
            }
          }

          // 其他错误或达到最大重试次数
          if (retryCount >= maxRetries) {
            console.error(`❌ 上传失败，已重试${maxRetries}次`);
            return '';
          }

          // 普通错误也等待一下再重试
          const waitTime = 2000 * retryCount;
          console.log(`⚠️ 上传错误，等待${waitTime/1000}秒重试...`);
          await new Promise(resolve => setTimeout(resolve, waitTime));
        }
      }

      return '';
    });
  }

  /**
   * 获取飞书文件的临时URL
   */
  async getFileInfo(appToken: string, fileToken: string): Promise<{url: string} | null> {
    const maxRetries = 3;
    let retryCount = 0;

    while (retryCount < maxRetries) {
      try {
        // 使用飞书API获取文件信息
        const result = await this.client.drive.media.batchGetTmpDownloadUrl({
          params: {
            file_tokens: [fileToken]
          }
        });

        // 检查是否是频率限制错误
        if (result.code === 99991400) {
          retryCount++;
          if (retryCount < maxRetries) {
            const waitTime = Math.min(2000 * Math.pow(2, retryCount), 10000);
            console.log(`⚠️ API限制，等待${waitTime/1000}秒重试...`);
            await new Promise(resolve => setTimeout(resolve, waitTime));
            continue;
          } else {
            console.error(`❌ API频率限制，已重试${maxRetries}次`);
            return null;
          }
        }

        if (result.code !== 0) {
          console.error(`❌ 获取文件URL失败: ${result.msg}`);
          return null;
        }

        if (!result.data?.tmp_download_urls?.[0]?.tmp_download_url) {
          console.error("❌ 响应数据格式错误");
          return null;
        }

        const downloadUrl = result.data.tmp_download_urls[0].tmp_download_url;
        return { url: downloadUrl };

      } catch (error) {
        retryCount++;
        if (retryCount < maxRetries) {
          const waitTime = 2000 * retryCount;
          console.log(`⚠️ 网络错误，等待${waitTime/1000}秒重试...`);
          await new Promise(resolve => setTimeout(resolve, waitTime));
        }
      }
    }

    console.error(`💥 获取文件信息最终失败，已重试 ${maxRetries} 次`);
    return null;
  }

  async downloadFile(fileToken: string, appToken: string, fileFolder: string): Promise<string> {
    //create the file folder if not exists
    if (!fs.existsSync(fileFolder)) {
      fs.mkdirSync(fileFolder, { recursive: true });
    }

    try {
      // 方法1: 尝试使用临时下载URL
      console.log(`尝试获取文件 ${fileToken} 的临时下载URL...`);
      const fileInfo = await this.getFileInfo(appToken, fileToken);

      if (fileInfo && fileInfo.url) {
        console.log(`获取到临时下载URL，正在下载...`);
        const filePath = fileFolder + '/' + fileToken + '.png';

        // 使用axios下载文件
        const response = await require('axios')({
          method: 'GET',
          url: fileInfo.url,
          responseType: 'stream'
        });

        const writer = fs.createWriteStream(filePath);
        response.data.pipe(writer);

        return new Promise((resolve, reject) => {
          writer.on('finish', () => {
            console.log("download file to: " + filePath);
            resolve(filePath);
          });
          writer.on('error', reject);
        });
      }
    } catch (error) {
      console.log(`临时URL下载失败，尝试直接下载: ${error}`);
    }

    try {
      // 方法2: 回退到直接下载
      console.log(`尝试直接下载文件 ${fileToken}...`);
      const resp = await this.client.drive.v1.media.download({
        path: {
          file_token: fileToken,
        },
      });

      const filePath = fileFolder + '/' + fileToken + '.png';
      console.log("download file to: " + filePath);
      await resp.writeFile(filePath);

      return filePath;
    } catch (directDownloadError) {
      console.error(`直接下载也失败:`, directDownloadError);

      // 方法3: 尝试使用不同的API端点
      try {
        console.log(`尝试使用备用下载方法...`);
        const resp = await this.client.drive.media.download({
          path: {
            file_token: fileToken,
          },
        });

        const filePath = fileFolder + '/' + fileToken + '.png';
        console.log("download file to: " + filePath);
        await resp.writeFile(filePath);

        return filePath;
      } catch (finalError:any) {
        console.error(`所有下载方法都失败了 ${fileToken}:`, finalError);
        throw new Error(`无法下载文件 ${fileToken}: ${finalError.message}`);
      }
    }
  }
  async getBitableFields(appToken: string, tableId: string): Promise<string[]> {
    const result = await this.client.bitable.appTableField.list({
      path: {
        app_token: appToken,
        table_id: tableId,
      }
    });
    if (result.code !== 0) {
      throw new Error("Get bitable fields error: " + JSON.stringify(result));
    }
    return result.data?.items?.map((item: any) => item.field_name) || [];
  }
}


export default LarkClient;
